/**
 * 
 */
package juf.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

import javax.swing.*; 

import juf.FieldProperty;
import juf.MethodProperty;
import juf.Property;
import juf.Annotations.Order;
import juf.adapter.Adapter;
import juf.adapter.AdapterFactory;

/**A lightweight form generated from, and indirectly bound to, an arbitrary object
 * 
 * @author Dale Halliwell
 */
@SuppressWarnings("serial")
public class JufForm extends JPanel implements ActionListener { 
	
	private Object pojo; //the pojo that this instance of the form is bound to	
	private ArrayList<Adapter> adapterList = new ArrayList<Adapter>(); //keeps track of all the juf components of the form
	private JLabel warningLabel;
	private JToolBar toolBar;
	
	public JufForm(Object pojo){
		this.pojo = pojo;
		
		this.setLayout(new BoxLayout(this,BoxLayout.PAGE_AXIS));
		
		 //Create the toolbar
        toolBar = new JToolBar("Tool Bar");
        addButtons(toolBar);
        this.add(toolBar);
     	
		warningLabel = new JLabel("<html><br>One or more values were not valid.<br>Changes were not saved.<br></html>"); //this shows in case any component was not validated
		warningLabel.setForeground(Color.red);
		warningLabel.setVisible(false);
		JPanel warningPanel = new JPanel();
		warningPanel.add(warningLabel,BorderLayout.LINE_START);
		this.add(warningPanel);

		//go over the object's properties, adding JufBoundComponents as needed
		try {
			addComponents();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			System.out.println("The JufForm failed to add a component because of an Illegal Argument Exception. See trace below \n");
			e.printStackTrace();
		}
	}
	
		
	/**
	 * Scan over the object, adding components which are bound to its fields and accessor-pairs
	 */
	@SuppressWarnings("unchecked")
	private void addComponents(){
		ArrayList<Property> properties = new ArrayList<Property>(); 
		Class pojoclass = pojo.getClass();
		
		//go through its public fields
		for(Field f: pojoclass.getFields())	properties.add(new FieldProperty(f, pojo)); //creating a property for each field and adding to the list  
	
		//want to use a listiterator for convenience
		List list = Arrays.asList(pojoclass.getMethods());
		ArrayList<Method> methodlist = new ArrayList<Method>(list); //converted to an arraylist
		
		ListIterator<Method> i = methodlist.listIterator(); //iterate over the list
		//go through methods, matching pairs of accessors, or single accessors
		while(i.hasNext()){
			boolean isFound = false; //has a match been found?
			//find the next getter
			Method currenti = i.next();
			if(MethodProperty.isGetter(currenti)){
				ListIterator<Method> j = methodlist.listIterator(); //iterate over the list again
				while(j.hasNext() && !isFound){
					//search for a setter matching the name of currenti(without the "get" or "is" or "set" prefix)
					Method currentj = j.next();
					if((MethodProperty.trimName(currentj).equals(MethodProperty.trimName(currenti))) && MethodProperty.isSetter(currentj))
					{	//if a matching setter was found
						isFound = true;					
						properties.add(new MethodProperty(currenti, currentj, pojo)); //create the editable methodproperty and add to list
						//System.out.println("method property: " + currenti.getName() + ", " + currentj.getName());
					}
				} //end while j.hasNext..
				//if a matching setter was not found
				if(!isFound){
					properties.add(new MethodProperty(currenti, pojo)); //create a readonly methodproperty and add to list
					//System.out.println("method property: " + currenti.getName());
				}	
			}//end if isGetter..
		} //end while i.hasNext..
		
		//next, create an adapter for each property
		//the JufComponent via AdapterFactory will decide which adapter suits which datatype
		for(Property p: properties){
			Adapter a = AdapterFactory.createAdapter(p);
			adapterList.add(a);			
		}
		
		reOrder();
	
	}//end addComponents
	
	/**
	 * checks if an adapter has an order annotation
	 * @param a
	 * @return true if there is an ordering specified for the adapter
	 */
	public boolean adapterHasOrder(Adapter a){
		Property p = a.getProperty();
		Annotation[] annotations = p.getAnnotations();
		for (Annotation ann : annotations){
			//get the Order class annotation 
			if (ann.annotationType().equals(Order.class)) return true;	
		}
		return false;
	}
	
	/**
	 * returns a list of the adapters that have an order specified for them
	 * @param aList
	 * @return
	 */
	public ArrayList<Adapter> getAdaptersWithOrder(ArrayList<Adapter> aList){
		
		ArrayList<Adapter> orderedList = new ArrayList<Adapter>();
		
		for (Adapter a: aList)
			if (adapterHasOrder(a)) orderedList.add(a);
		
		return orderedList;
	}
	
	public ArrayList<Adapter> getAdaptersWithNoOrder(ArrayList<Adapter> aList){
		
		ArrayList<Adapter> orderedList = new ArrayList<Adapter>();
		
		for (Adapter a: aList)
			if (!adapterHasOrder(a)) orderedList.add(a);
		
		return orderedList;
	}
	
	/**
	 * returns whether  of adapters that do not have an order specified
	 * @param aList
	 * @return
	 */
	public boolean adapterListHasOrder(ArrayList<Adapter> aList){		
		for (Adapter a : aList)		
			if (adapterHasOrder(a)) return true; //get the Order class annotation 				

		return false;
	}
	
	/**
	 * get the order specified for some adapter
	 * @param adap
	 * @return
	 */
	public int getOrder(Adapter adap){
		
		Property p = adap.getProperty();
		Annotation[] annotations = p.getAnnotations();
		int order = 0;
		for (Annotation ann : annotations){
			//get the Order class annotation 
			if (ann.annotationType().equals(Order.class)){			
					Order annotation = (Order) ann;
					order = annotation.value();
			}
			
		}
		return order;
	}
	
	/**
	 * adds adapters to the panel first according to the order specified
	 * and then adds the rest that have no order to the panel
	 */
	@SuppressWarnings("unchecked")
	public void reOrder(){

		ArrayList<Adapter> clonedList = (ArrayList<Adapter>) adapterList.clone();
		ArrayList<Adapter> orderedList = new ArrayList<Adapter>();	
		//initialise the ordered list
		for (int i=0; i<clonedList.size()-1; i++) orderedList.add(null);
		//while there are still adapters left
		while (!clonedList.isEmpty()){		
			//if there are adapters in the list that have an order specified
			if (adapterListHasOrder(clonedList)){
				//then find all the adapters with order
				ArrayList<Adapter> tempList = getAdaptersWithOrder(clonedList);
				for (Adapter t: tempList){
					//add each adapter to the ordered list 
					//the index will be one less than the order number specified
					orderedList.add(getOrder(t)-1, t);					
					clonedList.remove(t);
				}
			}
			//if there are no more adapters left that have an order
			else{
				ArrayList<Adapter> tempList = getAdaptersWithNoOrder(clonedList);
				//then add the rest of the adapters to the list
				for (Adapter t: tempList){
					orderedList.add(t);	
					clonedList.remove(t);
				}
			}
			
		}
		
		//go through the list and add to the panel
		for (int i=0; i<orderedList.size(); i++){
			if (orderedList.get(i)!=null){
				Adapter a = orderedList.get(i);
				this.add((JPanel) a);	
			}
		}
	
	}
	
	

	/**
	 * save all the values entered in the form back to the object
	 */
	public void save(){
		this.warningLabel.setVisible(false);
		for (Adapter a: adapterList){
			
			try {
				a.save();
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * validate all the values entered in the form 
	 * return true if all values are valid
	 */
	public boolean validateField(){		
		ArrayList<Boolean> valid = new ArrayList<Boolean>();
		for (Adapter a: adapterList) valid.add(a.validateField());

		if (valid.contains(false)){
			this.warningLabel.setVisible(true);
			this.revalidate();
			this.repaint();
			return false;		
		}
		else return true;
	}
	
	/**
	 * reload the form with the values from the object
	 */	
	public void refresh(){
		this.warningLabel.setVisible(false);
		for (Adapter a: adapterList) a.refresh();
	}
	
	
	 /** Adds buttons to the toolbar
     * @param toolBar JToolBar to add the buttons to
     */
    protected void addButtons(JToolBar toolBar) {

        ImageIcon save = new ImageIcon("images/save.gif"); 
        ImageIcon refresh = new ImageIcon("images/refresh_icon.gif"); 
    	
        JButton button = null;

        //first button
        button = JufInterface.makeToolsButton(refresh, "Refresh","Refresh form", this);
        toolBar.add(button);

        //third button
        button = JufInterface.makeToolsButton(save, "Save", "Save form", this);
        toolBar.add(button);
    }


    
    /** Listens to the button-click events
     * 
     * (non-Javadoc)
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        // Handle each button.
        //get the instance selected
        //then clone that instance and make the juf instance list return that instance    
        if ("Refresh".equals(cmd)) refresh(); //first button clicked      
        else if ("Save".equals(cmd) && validateField()) save(); // third button clicked
    }



    
    
}
